﻿#include<stdio.h>
#include<stdlib.h>
//枚举
/*
枚举的优点：
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符⽐较枚举有类型检查，更加严谨。
3. 便于调试，预处理阶段会删除 #define 定义的符号
4. 使⽤⽅便，⼀次可以定义多个常量
5. 枚举常量是遵循作⽤域规则的，枚举声明在函数内，只能在函数内使⽤
*/
enum optional {
	EXIT,
	ADD,
	MUL,
	DIV,
	SUB
};
void menu() {
	printf("*******************************************\n");
	printf("******  0.exit  ******  1.add  ************\n");
	printf("******  2.mul   ******  3.div  ************\n");
	printf("******  4.sub   ******         ************\n");
	printf("*******************************************\n");
}
void test01() {
	int input;
	do {
		menu();
		printf("请输入两个操作数\n");
		int n1, n2;
		scanf_s("%d%d", &n1, &n2);
		printf("请选择>:");
		scanf_s("%d", &input);
		switch (input) {
		case ADD:
			printf("%d\n", n1 + n2);
			break;
		case MUL:
			printf("%d\n", n1 * n2);
			break;
		case DIV:
			printf("%d\n", n1 / n2);
			break;
		case SUB:
			printf("%d\n", n1 - n2);
			break;
		case EXIT:
			printf("退出\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}//这里的枚举应用提高了代码的可读性
	} while (input);
}


//联合体
union u1 {
	char ch;
	int n;
};//这里联合体的大小至少是里面成员变量大小的最大值，为什么是“至少”，因为联合体的大小必须是其成员最大对齐数的整数倍

union u2 {
	char s[5];
	int n;
};//首先，s的对齐数值是其元素char类型的大小，所以对齐数是1，而n对齐数是4，但s的大小为5，
//且必须满联合体的大小是其成员最大对齐数的整数倍，所以sizeof(union u2)=8
void test02() {
	printf("%d\n", sizeof(union u1));//4
	printf("%d\n", sizeof(union u2));//8
}

//根据联合体在内存中这种独特的储存方式，决定了它每次创建的对象只能访问其中一个成员，因为它们的成员在内存中的位置都是重叠的


//动态内存管理
//malloc
void test03() {
	int arr[10] = { 0 };//这里创建一个数组，它会向内存申请40个字节空间
	int* parr = arr;
	int* pb = (int*)malloc(sizeof(int) * 10);//这里通过malloc来动态开辟一块数组，它同样向内存申请了40个字节空间
	if (pb == NULL) {
		perror("malloc");//动态申请内存也可能失败
	}
	free(pb);
	pb = NULL;
}//其次之所以强转为(int*)也是因为malloc这个库函数的返回值是其所申请空间的起始地址，而起始地址类型为整形指针，所以强转为整形指针
/* 
 那么它们的区别在哪？
 首先要了解一个概念，是关于内存的
 1.内存分为 静态区 栈区 堆
 2.局部变量创建后都在栈区存放，这是因为局部变量出了生命周期后会被销毁，所以类似这种临时性质的变量包括函数传值调用产生的参数
 都是存储在这里，而静态区则是存放全局变量，而堆区则存放动态申请内存的变量

 同通以上内容便可总结出它们的区别
 1.它们存在于不同的内存区域中，明显在堆区申请的内存更为稳定和长久。
 2.数组变量的大小是一个定值，而malloc动态申请的内存则可以变化。
*/
//free
/*
malloc在申请空间使用后应主动执行销毁和释放，因为malloc申请的内存空间都在堆区，堆区不像栈区会自动释放空间。其次，
如果不进行free操作，那么在一些大型程序运行时malloc申请的空间会一直存在知道程序结束，即时程序不用它时它也会存在，如果有很多的malloc申请的空间
都不进行free操作，那么随着程序的运行，占用内存会越来越大导致程序在结束之前崩溃。

free之后也要将free的内存地址置为空即NULL
free 的地址只能是动态申请的内存地址，也不能free野指针
*/


//calloc 和malloc的功能类似但参数不一样，calloc需要传两个参数 1.单个变量内存大小的数量 2.单个变量内存大小
//其次calloc还会对内存进行初始化，即将内存每个字节内容初始化为0而不是像malloc一样里面存的是随机值
void test04() {
	int* parr = (int*)calloc(10, sizeof(int));//申请10个单个变量大小为sizeof(int)字节的内存  一共是40个字节
	if (parr == NULL) {
		perror("calloc");
		return;
	}
	for (int i = 0; i < 10; i++) {
		printf("%d", parr[i]);
	}//0000000000
	free(parr);
	parr = 0;
}


//realloc
/*
* realloc这个函数起到调节已经动态申请的内存空间大小的作用
* 即缩小或放大所申请的内存
* 其次realloc在放大内存后返回的地址不一定是原来空间的地址，因为realloc往后所延申的空间可能已经被占用，此时
* realloc会重新申请一块空间这块空间的大小即时调整后的大小，但原来空间的内容也会原封不动的迁移过来。
* 根据以上内容可以推断使用realloc时不能直接用原来空间地址接收，要用中间值接收，
* 因为原来空间是完好的而realloc可能会申请失败返回空地址
*/
void test05() {
	int* parr = (int*)malloc(sizeof(int) * 10);
	if (parr == NULL) {
		perror("malloc");
		return;
	}
	int* tmp = (int*)realloc(parr, sizeof(int) * 20);//将原来的空间从40个字节延申到80个字节
	if (tmp == NULL) {
		perror("realloc");
		return;
	}
	parr = tmp;//保证realloc申请的空间没有问题后在赋值给原空间地址
	free(parr);
	parr = NULL;
}
void test07() {
	int* parr = (int*)realloc(NULL,sizeof(int)*10);//realloc也可以起到malloc的作用
	if (parr == NULL) {
		perror("realloc");
		return;
	}
	free(parr);
	parr = NULL;
}
int main() {
	//test01();
	//test02();
	//test04();
	test07();
	return 0;
}